﻿
using NDI.Properties;
using System;
using System.Collections.Generic;
namespace NDI
{
    public class Container : IDisposable
    {

        #region Fields

        private Container _parentContainer;
        private Dictionary<ServiceKey, ServiceEntry> _entries = new Dictionary<ServiceKey, ServiceEntry>();
        private Stack<WeakReference> disposables = new Stack<WeakReference>();

        #endregion

        #region Constructor

        public Container()
        {

        }

        public Container(Container parentCotainer)
        {
            this._parentContainer = parentCotainer;
        }

        public void Dispose()
        {
            while (disposables.Count > 0)
            {
                WeakReference wr = disposables.Pop();
                if (wr.IsAlive)
                {
                    var disposable = wr.Target as IDisposable;
                    disposable.Dispose();
                }
            }
            while (_disposeContainers.Count > 0)
            {
                _disposeContainers.Pop().Dispose();
            }
        }
        #endregion

        #region Reigster

        internal void RegisterImpl(ServiceKey key, ServiceEntry entry)
        {
            //这个地方不用add，这样可以避免异常进行替换操作，在不同容器见进行隔离分享实体时用到
            _entries[key] = entry;
        }

        #endregion

        #region Resolve

        public TService Resolve<TService>()
        {
            return ResolveImpl<TService, Func<Container, TService>>(null, factory => factory.Invoke(this), true);
        }

        public TService Resolve<TService, TArg>(TArg arg)
        {
            return ResolveImpl<TService, Func<Container, TArg, TService>>(null, factory => factory.Invoke(this, arg), true);
        }

        public TService Resolve<TService, TArg, TArg1>(TArg arg, TArg1 arg1)
        {
            return ResolveImpl<TService, Func<Container, TArg, TArg1, TService>>(null, factory => factory.Invoke(this, arg, arg1), true);
        }

        public TService ResolveNamed<TService>(string name)
        {
            return ResolveImpl<TService, Func<Container, TService>>(name, factory => factory.Invoke(this), true);
        }

        #region TryResolve

        public TService TryResolve<TService>()
        {
            return ResolveImpl<TService, Func<Container, TService>>(null, factory => factory.Invoke(this), false);
        }

        public TService TryResolve<TService, TArg>(TArg arg)
        {
            return ResolveImpl<TService, Func<Container, TArg, TService>>(null, factory => factory.Invoke(this, arg), false);
        }

        public TService TryResolve<TService, TArg, TArg1>(TArg arg, TArg1 arg1)
        {
            return ResolveImpl<TService, Func<Container, TArg, TArg1, TService>>(null, factory => factory.Invoke(this, arg, arg1), false);
        }

        #endregion

        #endregion

        #region ResolveImpl

        private TService ResolveImpl<TService, TFunc>(string name, Func<TFunc, TService> invoker, bool throwIfMissing)
        {
            var key = new ServiceKey(typeof(TService), typeof(TFunc)) { Name = name };

            ServiceEntry entry = GetEntry(key);

            if (entry != null)
            {
                switch (entry.Scope)
                {
                    //重用的情况
                    case ReuseScope.Hierarchy:

                        if (entry.Container != this)
                        {
                            return entry.Container.ResolveImpl(name, invoker, throwIfMissing);
                        }
                        else
                        {
                            if (entry.Instance == null)
                            {
                                entry.Instance = CreateInstance<TService, TFunc>(entry, invoker);
                            }
                        }
                        return(TService) entry.Instance;
                    case ReuseScope.Container:

                        ServiceEntry containerEntry = null;
                        if (entry.Container != this)
                        {
                            containerEntry = entry.CloneFor(this);//各自复制自己的SerivceEntry重新进行注册
                            this.RegisterImpl(key, containerEntry);
                        }
                        else
                        {
                            containerEntry = entry;
                        }

                        if (containerEntry.Instance == null)
                        {
                            containerEntry.Instance = CreateInstance<TService, TFunc>(containerEntry, invoker);
                        }
                        return (TService)containerEntry.Instance;
                    //不重用的情况
                    case ReuseScope.None:
                        return CreateInstance<TService, TFunc>(entry, invoker);
                    default:
                        throw new NDIException(Resources.UnkownScope);
                }
            }

            if (throwIfMissing)
            {
                throw new NDIException();
            }
            else
            {
                return default(TService);
            }
        }

        private TService CreateInstance<TService, TFunc>(ServiceEntry entry, Func<TFunc, TService> invoker)
        {
            TService instance = invoker((TFunc)entry.Factory);
            if (entry.Owner == Owner.Container && instance is IDisposable)
            {
                WeakReference wr = new WeakReference(instance);
                disposables.Push(wr);
            }
            return instance;
        }

        private ServiceEntry GetEntry(ServiceKey key)
        {
            ServiceEntry entry = null;
            _entries.TryGetValue(key, out entry);
            if (entry == null && _parentContainer != null)
            {
                entry = _parentContainer.GetEntry(key);
            }
            return entry;
        }

        #endregion

        #region ChildContainer

        private Stack<Container> _disposeContainers = new Stack<Container>();

        public Container CreatChildContainer()
        {
            var container = new Container(this);
            _disposeContainers.Push(container);
            return container;
        }

        #endregion

    }
}
